分类
联系方式
  1. 新浪微博
  2. E-mail

Flutter Sembast

介绍

Flutter 下数据库有 Hive、Moor、SQFlite,还有 Sembast。

数据库:将管理良好的结构化信息,以数码方式存储在电子设备上的软件。

关系型数据库:通过表、列和行的形式管理数据,效率最高,扩展性良好。

非关系型(NoSQL):它允许非结构化和半结构化的数据被存储和操作(与关系型数据库相反,关系型数据库定义了所有插入数据库的数据必须如何组成)。随着网络应用变得越来越普遍和复杂,NoSQL数据库逐渐流行起来。

名字的来源:Sembast db stands for Simple Embedded Application Store database.

引用依赖

path_provider: ^1.6.10
sembast: ^2.4.4+3

数据格式

针对的是单进程环境,数据库是一个单文件,在打开时全部载入内存。改动添加到文件的末尾,并会定期对文件进行压缩清理。还支持加密。整个数据库文件是一个 JSON 文件。

整体上是一个 K-V 存储结构,数据库包含 3 个字段:

{“key”:1, ”store”:”StoreName”, ”value”:{}}

其中:

  • key:是一个全局自增的主键
  • store:数据库支持多数据表,这个字段表示数据对应表名
  • value:具体值,是一个 JSON 格式

Model

一个标准的基于原生 Dart 的 Model 定义方式如下:

class Cake {
  final int id;
  final String name;
  final int yummyness;

  Cake({this.id, this.name, this.yummyness});

  Map<String, dynamic> toMap() {
    return {
      'name': this.name,
      'yummyness': this.yummyness
    };
  }

  factory Cake.fromMap(int id, Map<String, dynamic> map) {
    return Cake(
      id: id,
      name: map['name'],
      yummyness: map['yummyness'],
    );
  }

  Cake copyWith({int id, String name, int yummyness}){
    return Cake(
      id: id ?? this.id,
      name: name ?? this.name,
      yummyness: yummyness ?? this.yummyness,
    );
  }
}

这种写法过于繁琐,后来我又学会了基于 built_value 来创建 Entity。

架构模式

学到一种比较好的架构模式。用到的依赖有 get_it、 path_provider、path。

初始化类

首先有一个类专门负责 App 的初始化:

import 'package:get_it/get_it.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';

class Init {
  static Future initialize() async {
    await _initSembast();
  }

  static Future _initSembast() async {
    final appDir = await getApplicationDocumentsDirectory();
    await appDir.create(recursive: true);
    final databasePath = join(appDir.path, "sembast.db");
    final database = await databaseFactoryIo.openDatabase(databasePath);
    GetIt.I.registerSingleton<Database>(database);
  }
}

其中进行了数据库的创建与初始化,后面有了 Repository 之后也在这里面进行初始化。 getApplicationDocumentsDirectory 是 path_provider 库里面的, join 是 path 库里面的。这里面打开数据库之后,通过 GetIt 把数据库给作为单例存起来了。

创建 Repository

这里比较讲究,先创建了一个抽象类:

import 'cake.dart';

abstract class CakeRepository {

  Future<int> insertCake(Cake cake);

  Future updateCake(Cake cake);

  Future deleteCake(int cakeId);

  Future<List<Cake>> getAllCakes();
}

之后创建实现类:

import 'package:get_it/get_it.dart';
import 'package:sembast/sembast.dart';
import 'package:sembast_tutorial/cake.dart';
import 'package:sembast_tutorial/cake_repository.dart';

class SembastCakeRepository extends CakeRepository {
  final Database _database = GetIt.I.get();
  final StoreRef _store = intMapStoreFactory.store("cake_store");

  @override
  Future<int> insertCake(Cake cake) async {
    return await _store.add(_database, cake.toMap());
  }

  @override
  Future updateCake(Cake cake) async {
    await _store.record(cake.id).update(_database, cake.toMap());
  }

  @override
  Future deleteCake(int cakeId) async{
    await _store.record(cakeId).delete(_database);
  }

  @override
  Future<List<Cake>> getAllCakes() async {
    final snapshots = await _store.find(_database);
    return snapshots
        .map((snapshot) => Cake.fromMap(snapshot.key, snapshot.value))
        .toList(growable: false);
  }
}

为什么要使用这种抽象类加实现类的方式?答案是能够支持多种数据源,比如: SembastCakeRepository 、 SQLiteCakeRepository 、 FirebaseCakeRepository 。

扩展 init

  static Future initialize() async {
    await _initSembast();
    _registerRepositories();
  }

  static _registerRepositories(){
    GetIt.I.registerLazySingleton<CakeRepository>(() => SembastCakeRepository());
  }

主 App FutureBuilder

import 'package:flutter/material.dart';
import 'package:sembast_tutorial/home_page.dart';
import 'package:sembast_tutorial/init.dart';

void main() => runApp(CakeApp());

class CakeApp extends StatefulWidget {
  @override
  _CakeAppState createState() => _CakeAppState();
}

class _CakeAppState extends State<CakeApp> {
  final Future _init =  Init.initialize();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My Favorite Cakes',
      home: FutureBuilder(
        future: _init,
        builder: (context, snapshot){
          if (snapshot.connectionState == ConnectionState.done){
            return HomePage();
          } else {
            return Material(
              child: Center(
                child: CircularProgressIndicator(),
              ),
            );
          }
        },
      ),
    );
  }
}

网络资源

Sembast: NoSQL Database

Tutorial: Sembast as local data storage in Flutter